# Module 'parser'
#
# Analyse la sortie du programme Panel Editor qui est sous forme
# de S-expressions (ce programme étant écrit en Scheme, il ne peut nous
# aider à écrire des S-expressions).
#
# Voyez les notes à la fin du fichier.


whitespace = ' \t\n'
operators = '()\''
separators = operators + whitespace + ';' + '"'


# Divise une chaîne en unités lexicales.
# Renvoie une liste d'unités lexicales (des chaînes, en fait).
#
def tokenize_string(s):
	tokens = []
	while s:
		c = s[:1]
		if c in whitespace:
			s = s[1:]
		elif c = ';':
			s = ''
		elif c = '"':
			n = len(s)
			i = 1
			while i < n:
				c = s[i]
				i = i+1
				if c = '"': break
				if c = '\\': i = i+1
			tokens.append(s[:i])
			s = s[i:]
		elif c in operators:
			tokens.append(c)
			s = s[1:]
		else:
			n = len(s)
			i = 1
			while i < n:
				if s[i] in separators: break
				i = i+1
			tokens.append(s[:i])
			s = s[i:]
	return tokens


# Divise un fichier entier en unités lexicales (l'argument doit être
# un objet fichier, pas un nom de fichier).
# Renvoie une liste d'unités lexicales (des chaînes, en fait).
#
def tokenize_file(fp):
	tokens = []
	while 1:
		line = fp.readline()
		if not line: break
		tokens = tokens + tokenize_string(line)
	return tokens


# L'exception déclénchée par parse_expr.
#
syntax_error = 'erreur de syntaxe'


# Analyse une S-expression.
# L'entrée doit être une liste d'unités lexicales, du
# même style que celles renvoyées par les fonctions
# tokenize_*().
# Renvoie une paire (expr, unités-lexicales) où expr
# est une liste représentant la S-expression et
# unités-lexicales une liste contenant les unités lexicales
# restantes.
# Cette fonction peut déclencher une exception syntax_error.
#
def parse_expr(tokens):
	if (not tokens) or tokens[0] <> '(':
		raise syntax_error, '"(" attendu'
	tokens = tokens[1:]
	expr = []
	while 1:
		if not tokens:
			raise syntax_error, '")" manquant'
		if tokens[0] = ')':
			return expr, tokens[1:]
		elif tokens[0] = '(':
			subexpr, tokens = parse_expr(tokens)
			expr.append(subexpr)
		else:
			expr.append(tokens[0])
			tokens = tokens[1:]


# Analyse un fichier entier (l'argument doit être un objet fichier,
# pas un nom de fichier). Renvoie une liste des S-expressions contenues
# au plus haut niveau du fichier, analysées.
#
def parse_file(fp):
	tokens = tokenize_file(fp)
	exprlist = []
	while tokens:
		expr, tokens = parse_expr(tokens)
		exprlist.append(expr)
	return exprlist


# EXEMPLE :
#
# Passer la chaîne
#	'(hip (hip hourra))'
#
# à la fonction tokenize_string() renvoie la liste d'unités lexicales
# suivante :
#	['(', 'hip', '(', 'hip', 'hourra', ')', ')']
#
# Passer cette chaîne à la fonction parse_expr() renvoie l'expression
# suivante :
#	['hip', ['hip', 'hourra']]
# et une liste d'unités lexicales vide (car il ne reste plus aucune
# unité lexicale).
#
# Lorsqu'un fichier contenant l'exemple ci-dessus est passé à la fonction
# parse_file(), celle-ci renvoie une liste dont le seul élément est la
# sortie de la fonction parse_expr(), à savoir :
#	[['hip', ['hip', 'hourra']]]


# EN CE QUI CONCERNE LA DIVISION EN UNITÉS LEXICALES :
#
# Les commentaires commencent par un point-virgule (;) et continuent jusqu'à
# la fin de la ligne.
#
# Les unités lexicales sont séparées entre elles par des blancs, à part
# pour les caractères suivants qui forment toujours des unités lexicales
# à part entière (lorsqu'ils sont en dehors des chaînes) :
#	( ) '
# Les chaînes sont contenues entre guillemets doubles (") et l'antislash (\)
# sert à échapper les caractères spéciaux à l'intérieur des chaînes.